// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <memory>

#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/macros.h"
#include "base/message_loop/message_loop.h"
#include "base/test/simple_test_tick_clock.h"
#include "media/base/gmock_callback_support.h"
#include "media/base/null_video_sink.h"
#include "media/base/test_helpers.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using testing::_;
using testing::DoAll;
using testing::Return;

namespace media {

class NullVideoSinkTest : public testing::Test,
                          public VideoRendererSink::RenderCallback {
public:
    NullVideoSinkTest()
    {
        // Never use null TimeTicks since they have special connotations.
        tick_clock_.Advance(base::TimeDelta::FromMicroseconds(12345));
    }
    ~NullVideoSinkTest() override { }

    std::unique_ptr<NullVideoSink> ConstructSink(bool clockless,
        base::TimeDelta interval)
    {
        std::unique_ptr<NullVideoSink> new_sink(new NullVideoSink(
            clockless, interval,
            base::Bind(&NullVideoSinkTest::FrameReceived, base::Unretained(this)),
            message_loop_.task_runner()));
        new_sink->set_tick_clock_for_testing(&tick_clock_);
        return new_sink;
    }

    scoped_refptr<VideoFrame> CreateFrame(base::TimeDelta timestamp)
    {
        const gfx::Size natural_size(8, 8);
        return VideoFrame::CreateFrame(PIXEL_FORMAT_YV12, natural_size,
            gfx::Rect(natural_size), natural_size,
            timestamp);
    }

    // VideoRendererSink::RenderCallback implementation.
    MOCK_METHOD3(Render,
        scoped_refptr<VideoFrame>(base::TimeTicks,
            base::TimeTicks,
            bool));
    MOCK_METHOD0(OnFrameDropped, void());

    MOCK_METHOD1(FrameReceived, void(const scoped_refptr<VideoFrame>&));

protected:
    base::MessageLoop message_loop_;
    base::SimpleTestTickClock tick_clock_;

    DISALLOW_COPY_AND_ASSIGN(NullVideoSinkTest);
};

TEST_F(NullVideoSinkTest, BasicFunctionality)
{
    const base::TimeDelta kInterval = base::TimeDelta::FromMilliseconds(25);

    std::unique_ptr<NullVideoSink> sink = ConstructSink(false, kInterval);
    scoped_refptr<VideoFrame> test_frame = CreateFrame(base::TimeDelta());

    {
        SCOPED_TRACE("Waiting for sink startup.");
        sink->Start(this);
        const base::TimeTicks current_time = tick_clock_.NowTicks();
        const base::TimeTicks current_interval_end = current_time + kInterval;
        EXPECT_CALL(*this, Render(current_time, current_interval_end, false))
            .WillOnce(Return(test_frame));
        WaitableMessageLoopEvent event;
        EXPECT_CALL(*this, FrameReceived(test_frame))
            .WillOnce(RunClosure(event.GetClosure()));
        event.RunAndWait();
    }

    // Verify that toggling background rendering mode issues the right bit to
    // each Render() call.
    sink->set_background_render(true);

    // A second call returning the same frame should not result in a new call to
    // FrameReceived().
    {
        SCOPED_TRACE("Waiting for second render call.");
        WaitableMessageLoopEvent event;
        scoped_refptr<VideoFrame> test_frame_2 = CreateFrame(kInterval);
        EXPECT_CALL(*this, Render(_, _, true))
            .WillOnce(Return(test_frame))
            .WillOnce(Return(test_frame_2));
        EXPECT_CALL(*this, FrameReceived(test_frame)).Times(0);
        EXPECT_CALL(*this, FrameReceived(test_frame_2))
            .WillOnce(RunClosure(event.GetClosure()));
        event.RunAndWait();
    }

    {
        SCOPED_TRACE("Waiting for stop event.");
        WaitableMessageLoopEvent event;
        sink->set_stop_cb(event.GetClosure());
        sink->Stop();
        event.RunAndWait();
    }

    // The sink shouldn't have to be started to use the paint method.
    EXPECT_CALL(*this, FrameReceived(test_frame));
    sink->PaintSingleFrame(test_frame, false);
}

TEST_F(NullVideoSinkTest, ClocklessFunctionality)
{
    // Construct the sink with a huge interval, it should still complete quickly.
    const base::TimeDelta interval = base::TimeDelta::FromSeconds(10);
    std::unique_ptr<NullVideoSink> sink = ConstructSink(true, interval);

    scoped_refptr<VideoFrame> test_frame = CreateFrame(base::TimeDelta());
    scoped_refptr<VideoFrame> test_frame_2 = CreateFrame(interval);
    sink->Start(this);

    EXPECT_CALL(*this, FrameReceived(test_frame)).Times(1);
    EXPECT_CALL(*this, FrameReceived(test_frame_2)).Times(1);

    const int kTestRuns = 6;
    const base::TimeTicks now = base::TimeTicks::Now();
    const base::TimeTicks current_time = tick_clock_.NowTicks();

    SCOPED_TRACE("Waiting for multiple render callbacks");
    WaitableMessageLoopEvent event;
    for (int i = 0; i < kTestRuns; ++i) {
        if (i < kTestRuns - 1) {
            EXPECT_CALL(*this, Render(current_time + i * interval, current_time + (i + 1) * interval, false))
                .WillOnce(Return(test_frame));
        } else {
            EXPECT_CALL(*this, Render(current_time + i * interval, current_time + (i + 1) * interval, false))
                .WillOnce(
                    DoAll(RunClosure(event.GetClosure()), Return(test_frame_2)));
        }
    }
    event.RunAndWait();
    ASSERT_LT(base::TimeTicks::Now() - now, kTestRuns * interval);
    sink->Stop();
}

} // namespace media
